/* This file is part of BonQ - A simple HTTP-based messaging server.
 * Copyright (C) 2009 James Cook
 *
 * BonQ is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License, version 2, as published by the
 * Free Software Foundation.
 *
 * BonQ is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License, version 2,
 * for more details.
 *
 * You should have received a copy of the GNU General Public License, version
 * 2, along with BonQ.  If not, see:
 *
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 */
package com.bonkabonka.queue;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.FileWriter;
import java.util.HashMap;
import java.util.Map;

import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

/**
 * This class contains a set of Queues.  It provides methods to serialize its
 * state to/from disk, as well as creating, removing and listing what queues it
 * manages.
 * 
 * @author jamesc
 */
public class QueueBucket
{
    private String filename;
    private XStream xstream;
    private final HashMap<String, Queue> queues = new HashMap<String, Queue>();


    /**
     * The "magic" name of the root-level system queue.
     */
    public static final String SYSQ = "";
    
    // This particularly interesting way to initialize a map came from:
    // weblog.raganwald.com/2007/04/rails-style-creators-in-java-or-how-i.html
    private final Map<String, Class> types = new HashMap<String, Class>() {{
        put("persistent",   PersistentQueue.class);
        put("scripted",     ScriptedQueue.class);
        put("status",       StatusQueue.class);
        put("temporary",    TemporaryQueue.class);
        put("transient",    TransientQueue.class);
    }};

    /**
     * Construct a bucket using the given filename as a backing-store.
     * 
     * @param filename String containing the filename to serialize to
     * @throws java.io.IOException from load()
     */
    public QueueBucket(String filename)
        throws IOException
    {
        this.filename = filename;
        
        load();
    }

    /**
     * Fetch a queue.
     * 
     * @param name String containing the name of the queue
     * @return Queue if found, null if not
     */
    public Queue get(String name)
    {
        synchronized(queues)
        {
            return queues.get(name);
        }
    }

    /**
     * Add a queue.
     * 
     * @param name String containing the name of the queue
     * @param type String containing the type of the queue
     * @return Queue if successfully created
     * @throws java.lang.IllegalArgumentException on unknown type or duped name
     * @throws java.lang.InstantiationException from Class.newInstance()
     * @throws java.lang.IllegalAccessException from Class.newInstance()
     */
    public Queue add(String name, String type)
        throws IllegalArgumentException, 
               InstantiationException,
               IllegalAccessException
    {
        Class c = types.get(type);
        if(c == null)
        {
            throw new IllegalArgumentException("Unknown queue type");
        }
        
        Queue queue = (Queue)c.newInstance();
        queue.init(this, name);
        
        synchronized(queues)
        {
            if(queues.get(name) == null)
            {
                queues.put(name, queue);
            }
            else
            {
                throw new IllegalArgumentException("Queue already exists");
            }
        }
        
        return queue;
    }

    /**
     * Remove a queue.
     * 
     * @param path String containing the name of the queue
     * @return Queue if found
     * @throws java.lang.IllegalArgumentException if queue doesn't exist
     */
    public Queue remove(String path)
        throws IllegalArgumentException
    {
        Queue queue;
        
        synchronized(queues)
        {
            queue = queues.remove(path);
            if(queue == null)
            {
                throw new IllegalArgumentException("Queue doesn't exist");
            }
        }
        
        return queue;
    }

    /**
     * Get list of queue names.
     * 
     * @return String[] containing all the queue names (including SYSQ)
     */
    public String[] list()
    {
        synchronized(queues)
        {
            return queues.keySet().toArray(new String[0]);  // Yuck!
        }
    }

    /**
     * Load the queue map from disk.  This is really only designed to be called
     * once at construction time (hence the reason it's private).
     * 
     * @throws java.io.IOException from a variety of file-based activities
     */
    @SuppressWarnings("unchecked") // make the compiler whinge less
    private void load()
        throws IOException
    {
        xstream = new XStream(new DomDriver());
        for(String alias: types.keySet())
        {
            xstream.alias(alias, types.get(alias));
        }

        File file = new File(filename);

        if(file.exists())
        {
            synchronized(queues)
            {
                if(!queues.isEmpty())
                {
                    queues.clear();
                }

                FileReader in = new FileReader(file);
                queues.putAll((HashMap<String, Queue>)xstream.fromXML(in));
                in.close();
            }
        }
        
        queues.put(SYSQ, new SystemQueue());

        for(Queue queue: queues.values())
        {
            // Bit of magic here: the name variable will be pre-populated by
            // the xstream deserializer so getName() will do the right thing
            queue.init(this, queue.getName());
        }
    }

    /**
     * Write the map of queues out to disk.
     * 
     * @throws java.io.IOException from a variety of file-based activities
     */
    public void save()
        throws IOException
    {
        synchronized(queues)
        {
            Queue system = queues.remove(SYSQ);

            FileWriter out = new FileWriter(filename);
            xstream.toXML(queues, out);
            out.close();

            queues.put(SYSQ, system);
        }
    }
}
